using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using EnvDTE;
using EnvDTE80;
using System.Drawing;
using NDomain.Helpers;
using NDomain.Reflection;

namespace NDomain.DomainExplorer
{
    public partial class DomainExplorerControl : UserControl
    {
        private DTE2 dte2 = null;

        public DomainExplorerControl()
        {
            InitializeComponent();
        }

        public DTE2 DTE
        {
            get { return dte2;}
            set { dte2 = value; }
        }

        private void DomainExplorerControl_Load(object sender, EventArgs e)
        {
            foreach (Project project in dte2.Solution.Projects)
                DrawDomain(GetDomain(project));
        }

        private static DomainInfo GetDomain(Project project)
        {
            return new ProjectInspector(
                new DTE2ProjectInfoProvider(project), 
                new EntityProcessor(),
                new ValueObjectProcessor(), 
                new ServiceProcessor(), 
                new FactoryProcessor(),
                new RepositoryProcessor())
                .GetDomain();
        }

        private void DrawDomain(DomainInfo domain)
        {
            if (domain.Modules.Count > 0)
                DrawModules(treeView1.Nodes.Add(domain.Name).Nodes, domain.Modules);
        }

        private static void DrawModules(TreeNodeCollection nodes, IList<ModuleInfo> modules)
        {
            foreach(ModuleInfo module in modules)
                DrawModule(module, nodes);
        }

        private static void DrawModule(ModuleInfo module, TreeNodeCollection nodes)
        {
            TreeNode node = nodes.Add(module.Name);
            SetImageIndex(node, 0);
            AddModuleContextMenu(node);

            DrawModules(node.Nodes, module.Modules);

            foreach (AggregateInfo aggregate in module.Aggregates)
                DrawAggregate(node, aggregate);

            foreach (EntityInfo entity in module.Entities)
                DrawEntity(node, entity);

            foreach (ValueObjectInfo valueObject in module.ValueObjects)
                DrawValueObject(node, valueObject);

            foreach (ServiceInfo service in module.Services)
                DrawService(node, service);
        }

        private static void SetImageIndex(TreeNode node, int index)
        {
            node.ImageIndex = index;
            node.SelectedImageIndex = index;
        }

        private static void DrawAggregate(TreeNode parent, AggregateInfo aggregate)
        {
            TreeNode node = parent.Nodes.Add(aggregate.Name);
            SetImageIndex(node, 1);
            if (aggregate.Root != null)
                DrawEntity(node, aggregate.Root);
            foreach (EntityInfo entity in aggregate.Entities)
                DrawEntity(node, entity);
            foreach (ValueObjectInfo valueObject in aggregate.ValueObjects)
                DrawValueObject(node, valueObject);
        }

        private static void DrawService(TreeNode parent, ServiceInfo service)
        {
            SetImageIndex(parent.Nodes.Add(service.Name), 6);
        }

        private static void DrawValueObject(TreeNode parent, ValueObjectInfo valueObject)
        {
            SetImageIndex(parent.Nodes.Add(valueObject.Name), 3);
        }

        private static void DrawEntity(TreeNode parent, EntityInfo entity)
        {
            TreeNode node = parent.Nodes.Add(entity.Name);
            SetImageIndex(node, 2);
            AddEntityContextMenu(node);

            if (entity.Repository != null)
                node.Nodes.Add(entity.Repository.Name).ImageIndex = 4;

            if (entity.Factory != null)
                node.Nodes.Add(entity.Factory.Name).ImageIndex = 5;
        }
        
        private static void AddEntityContextMenu(TreeNode node)
        {
            AddContextMenu(node, "New Factory...", "New Repository...");
        }

        private static void MenuItem_Click(object sender, EventArgs e)
        {
            MenuItem menu = (MenuItem) sender;
            TreeNode treeNode = (TreeNode) menu.Parent.Tag;
            TreeNode newNode = treeNode.Nodes.Add(menu.Text.Remove(menu.Text.Length - 3).Replace(" ", ""));
            newNode.Tag = treeNode.Tag;
            if (menu.Text == "New Module...")
            {
                AddModuleContextMenu(newNode);
            }
            treeNode.Expand();
            newNode.TreeView.LabelEdit = true;
            newNode.BeginEdit();
        }

        private static void AddModuleContextMenu(TreeNode node)
        {
            AddContextMenu(node, "New Module...", "New Entity...", "New Value Object...");
        }

        private static void AddContextMenu(TreeNode node, params string[] texts)
        {
            node.ContextMenu = new ContextMenu();
            node.ContextMenu.Tag = node;
            foreach(string text in texts)
                node.ContextMenu.MenuItems.Add(new MenuItem(text, MenuItem_Click));
        }

        private void treeView1_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            CodeElement element = e.Node.Tag as CodeElement;
            if (element == null) return;

            element.ProjectItem.Open(Constants.vsViewKindPrimary).Activate();
            element.StartPoint.TryToShow(vsPaneShowHow.vsPaneShowCentered, 0);
        }

        private void treeView1_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
        {
            treeView1.LabelEdit = false;
            ProjectItems projecItems = null;// GetProjectItems(e.Node.Tag);
            switch (e.Node.Text)
            {
                case "NewModule":
                    e.Node.Tag = projecItems.AddFolder(e.Label, null).ProjectItems;
                    break;
                case "NewEntity":
                    CreateArtifact(e, projecItems, "Entity", null);
                    break;
                case "NewValueObject":
                    CreateArtifact(e, projecItems, "ValueObject", null);
                    break;
                case "NewFactory":
                    CreateArtifact(e, projecItems, "Factory", "\""+e.Node.Parent.Text.Remove(0, e.Node.Parent.Text.LastIndexOf(" ") + 1) + "\"");
                    break;
                case "NewRepository":
                    ProjectItem newItem = CreateArtifact(e, projecItems, "Repository", null);
                    CodeClass codeClass = GetFirtstCodeClass(newItem);
                    codeClass.AddImplementedInterface(
                        String.Format(@"ICollection<{0}>",
                                      e.Node.Parent.Text.Remove(0, e.Node.Parent.Text.LastIndexOf(" ") + 1)), -1);

                    break;
            }
        }

        private ProjectItem CreateArtifact(NodeLabelEditEventArgs e, ProjectItems projecItems, string AttributeName, string AttributeValue)
        {
            Solution2 soln = (Solution2) dte2.Solution;

            projecItems.AddFromTemplate(soln.GetProjectItemTemplate("Class", "CSharp"),
                                        e.Label + ".cs");

            ProjectItem newItem = projecItems.Item(e.Label + ".cs");

            ((FileCodeModel2) newItem.FileCodeModel).AddImport("NDomain", 0, "");

            CodeClass codeClass = GetFirtstCodeClass(newItem);
            codeClass.AddAttribute(AttributeName, AttributeValue, -1);
            codeClass.Access = vsCMAccess.vsCMAccessPublic;
            return newItem;
        }

        private static CodeClass GetFirtstCodeClass(ProjectItem newItem)
        {
            foreach (CodeNamespace codeNamespace in IEnumerableHelper.Filter<CodeNamespace>(newItem.FileCodeModel.CodeElements))
                foreach (CodeClass2 codeClass in IEnumerableHelper.Filter<CodeClass2>(codeNamespace.Children))
                    return codeClass;

            return null;
        }
    }
}